#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#      This file is distributed as part of the WebAMP2 project and is licensed
#      under the GPL version 3.
#
#      Copyright 2007 Chris Seickel <cseickel@gmail.com>
#
#      This program is free software; you can redistribute it and/or modify
#      it under the terms of the GNU General Public License version 3 as 
#      published by the Free Software Foundation.
#
#      This program is distributed in the hope that it will be useful,
#      but WITHOUT ANY WARRANTY; without even the implied warranty of
#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#      GNU General Public License version 3 for more details.
#
#      You should have received a copy of the GNU General Public License
#      along with this program; if not, write to:
#       Free Software Foundation, Inc.
#       51 Franklin Street, Fifth Floor
#       Boston, MA 02110-1301, USA


import mpdclient2
import time, urllib
from threading import Thread
import cgi
from observe import *
import codecs
import os, glob, pickle
import difflib
import database as db

RDF_header = """<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
           xmlns:s="http://webamp2/RDF#">
"""
def test():
    m = Player(host="192.168.1.2", port=6600, dbFile="/var/lib/mpd/tag_cache", 
               dbDir="/home/cseickel/dev/webamp2/static/rdf")
    #m.createRDFdbFromFile()
    #printgroup(m.groupedArtists)
    return m

def allsafe(s, escape=False):
    safe = u''
    if escape:
        s = cgi.escape(s).replace('"','&#34;').replace("'",'&#39;')
    for i in range(0, len(s)):
        try:
            # My own database had a STX character in the tag_cache.
            # This extra work is to ensure that no control characters
            # get into the RDF files.
            if (ord(s[i]) > 31):
                safe += s[i]
        except:
            safe += u'?'
    return safe


class ModifiedTimer(Thread):

    def __init__(self, interval, maxtime, target):
        self.__interval = interval
        self.__maxtime = maxtime
        self.__tottime = 0.0
        self.__target = target
        self.__flag = 0
        Thread.__init__(self, None, 'mytimer', None)

    def run(self):
        while self.__flag==0:
            time.sleep(self.__interval)
            self.__target()
            self.__tottime += self.__interval
        if self.__tottime >= self.__maxtime:
            print 'Timing out...'
        self.end()

    def end(self):
        self.__flag=1

class MyTimer:

    def __init__(self, interval, func):
        self.__interval = interval
        self.__timerfunc = func

    def __timerfunc(self):
        print 'Hello, this is the timer function!'

    def make_timer(self, interval):
        self.__interval = interval
        self.__timer = ModifiedTimer(self.__interval, 60.0, self.__timerfunc)
        self.__timer.start()


class Player:

    def __init__(self, host="localhost", port=6600, 
                 dbFile="/var/lib/mpd/tag_cache",
                 dbDir=None, base_url="http://localhost:8080"):
        """Container for mpd interface"""
        self.host = host
        self.port = port
        self.dbFile = dbFile
        if not dbDir: dbDir = os.path.join(os.getcwd(),"static", "rdf")
        self.dbDir = dbDir
        self.songs = []
        self.dirs = []
        self.playlists = [""]
        self.connected = False
        self.poller = None
        self.db = db
        self.db.BASE_URL = base_url
        self.dbVer = -1
        self.ready = False
        obs_curSong.connect(self.songChange)

    def init(self):
        stats = self.stats()
        self.createdb(stats)
        self.ready = True

    def start_poller(self):
        """Connect to mpd and start polling Timer if successfull"""
        if self.poller: self.poller.end()
        self.keepAlive = 10
        self.connected = self._getState()
        if self.connected:
            self.poller = ModifiedTimer(0.1, 60.0, self._getState)
            self.poller.start()
            print "MPD polling loop starting."
        else:
            self.poller = None        


    def _getState(self):
        """Update state information from mpd"""
        if self.keepAlive < 1:
            print "MPD polling loop is timing out."
            print "Nobody wants me...."
            self.poller.end()
            self.poller = None
            self.connected = False
        else:
            try:
                #self.keepAlive -= 1
                mpd = self.connect()
                s = mpd.status()
                if s.has_key("updating_db"):
                    obs_dbUpdating.value = True
                    obs_curSong.value = dict(pos=-2)
                    return True
                else:
                    if obs_dbUpdating.value == True:
                        stats = self.stats()
                        if obs_dbStats.value <> stats:
                            self.createdb(stats)
                    else:
                        obs_dbUpdating.value = False
                obs_state.value = s.state
                obs_volume.value = int(s.volume)
                obs_random.value = int(s.random)
                obs_repeat.value = int(s.repeat)
                if (s.state != "stop"):
                    t = s.time.split(":")
                    obs_totalTime.value = int(t[1])
                    obs_elapsedTime.value = int(t[0])
                    obs_curSong.value = mpd.playlistinfo(int(s.song))[0]
                else:
                    obs_totalTime.value = 0
                    obs_elapsedTime.value = 0
                    obs_curSong.value = None

                v = int(s.playlist)
                if (v <> obs_playlist.version):
                    info = mpd.playlistinfo()
                    obs_playlist.items = []
                    for item in info:
                        try: 
                            plid = self.songs.index(item.file.decode('utf-8'))
                            obs_playlist.items.append(str(plid))
                        except: obs_playlist.items.append("-1")
                    obs_playlist.times = [x["time"] for x in info]
                obs_playlist.version = v
                return True
            except:
                print "ERROR in mpd polling loop!"
                return False


    def connect(self):
        """Return connection to mpd"""
        return mpdclient2.connect(host=self.host, port=self.port)


    def play(self, songID=None):
        m = self.connect()
        try: 
            song = int(songID)
            r = m.play(song)
        except:
            r = m.play()      
        return r

    def stop(self):
        m = self.connect()
        r = m.stop()      
        return r

    def pause(self, b=True):
        m = self.connect()
        r = m.pause(b)     
        return r

    def next(self):
        m = self.connect()
        r = m.next()      
        return r

    def previous(self):
        m = self.connect()
        r = m.previous()     
        return r

    def random(self):
        m = self.connect()
        if obs_random.value == 1:
            r = m.random(0)     
        else:
            r = m.random(1)     
        return r

    def repeat(self):
        m = self.connect()
        if obs_repeat.value == 1:
            r = m.repeat(0)     
        else:
            r = m.repeat(1)     
        return r

    def playlistAppend(self, ids, mytype="file"):
        m = self.connect()
        if mytype=="artist":
            for id in ids:
                items = self.db.getSongsBy(id)
                for item in items:
                    m.add(item)
        if mytype=="album":
            for id in ids:
                items = self.db.getSongsOn(id)
                for item in items:
                    m.add(item.encode('utf-8'))
        if mytype=="dir":
            for id in ids:
                item = self.dirs[int(id)].encode('utf-8')
                m.add(item)    
        elif mytype=="file":
            for id in ids:
                item = self.songs[int(id)].encode('utf-8')
                m.add(item)    


    def playlistRemove(self, IDs):
        m = self.connect()
        IDs.sort()
        print "Removing the following playlist items: "
        print IDs
        # IDs are playlist positions, the songs are in a different position 
        # after each delete, so the position is offset by i to compensate.
        i = 0
        for ID in IDs:
            m.delete(ID-i)
            i += 1

    def playlistClear(self):
        m = self.connect()
        m.clear()
        #Sometimes mpd does not report the cleared playlist
        obs_playlist.items = []
        obs_playlist.version = -1

    def playlistLoad(self, ids):
        m = self.connect()
        print ids
        print self.playlists
        for id in ids:
            item = self.playlists[id]
            m.load(item)

    def playlistSave(self, name):
        m = self.connect()
        r = m.save(name)   
        return r

    def shuffle(self):
        m = self.connect()
        r = m.shuffle()
        return r

    def playlistMove(self, fromID, toID):
        m = self.connect()
        r = m.move(int(fromid),int(toid))        
        return r

    def seek(self, val):
        m = self.connect()
        s = m.status()
        if (s.state != "stop"):
            if str(val).isdigit():
                seekto = int(val)
            elif (val[:1]=="+"):
                seekto = int(elapsedTm)+int(val[1:])
            elif (val[:1]=="-"):
                seekto = int(elapsedTm)-int(val[1:])
            r = m.seek(int(s.song), seekto)       
        return r

    def volumeSet(self, vol):
        m = self.connect()
        if str(vol).isdigit():
            v = int(vol)
        elif (vol[:1]=="+"):
            s = m.status()
            v = int(s.volume)+int(vol[1:])
        elif (vol[:1]=="-"):
            s = m.status()
            v = int(s.volume)-int(vol[1:])
        r = m.setvol(int(v))   
        return r

    def getPlaylist(self, id=0, fname=""):
        m = self.connect()
        if id == 0:
            info = [x['playlist'] for x in m.lsinfo() if x.has_key('playlist')]
            for item in info:
                if item not in self.playlists: self.playlists.append(item)
            for pl in self.playlists:
                if pl not in info: self.playlists[self.playlists.index(pl)] = "[deleted]"    
            print self.playlists
            if fname == "": 
                fname = self.dbDir+"/playlists.xml"
                self.db.createRDFplaylists(self.playlists, fname)
            else:
                self.db.createRDF_playlist(id, self.playlists, fname)
        else:
            print self.playlists[int(id)]
            info = m.listplaylistinfo(self.playlists[int(id)])
            print info
            lst = [self.songs.index(x['file']) for x in info]
            print lst
            print fname
            self.db.createRDF_playlist(id, lst, fname)

    def stats(self):
        m = self.connect()
        stats = m.stats()
        del stats['uptime']
        del stats['playtime']
        return stats

    def update(self):
        if not obs_dbUpdating.value:
            m = self.connect()
            m.update()


    def createdb(self, stats=None):
        """Parse MPD's tag_cache file and convert to sqlite db."""
        if not stats: return False
        obs_dbUpdating.value = True
        print "Checking MPD database..."
        self.dbVer = stats['db_update']
        dbstats = db.getStats()
        songs = []
        print "MPD dbVersion:", self.dbVer
        print "SQLite dbVersion:", dbstats['db_update']
        if self.dbVer <> dbstats['db_update']:
            if os.path.exists(self.dbFile):
                f = codecs.open(self.dbFile, "r",'utf-8')
                lines = f.readlines()
                i = 1
                while (i < len(lines)):
                    line = lines[i]
                    if (line[:4] == "key:"): # Begins song description.
                        file = None
                        title = artist = album = ""
                        time = "0"
                        track = "0"
                        updated = 0
                        while (line[:6] <> "mtime:"):
                            line = lines[i]
                            tup = line.split(":", 1)
                            tup[1] = tup[1][1:-1] # Remove leading space and trailing newline
                            tup[1] = allsafe(tup[1])
                            if (tup[0] == "file"): file = tup[1]
                            elif (tup[0] == "Time"): time = tup[1]
                            elif (tup[0] == "Artist"): artist = tup[1]
                            elif (tup[0] == "Album"): album = tup[1]
                            elif (tup[0] == "Title"): title = tup[1]
                            elif (tup[0] == "Track"): track = tup[1]
                            elif (tup[0] == "mtime"): #Last line of song description
                                updated = int(tup[1])
                                i -= 1
                            i += 1
                        songs.append(dict(file=file, title=title, album=album, 
                                          artist=artist, track=track, time=time,
                                          updated=updated))
                    i += 1
            else:
                print "tag_cache not found, loading db from network."
                m = self.connect()
                info = m.listallinfo()
                updated = self.dbVer
                for s in info:
                    if s.has_key("file"):
                        title = artist = album = time = track = file = ""
                        if s.has_key("file"): file = allsafe(s.file)
                        if s.has_key("title"): title = allsafe(s.title)
                        if s.has_key("artist"): artist = allsafe(s.artist)
                        if s.has_key("album"): album = allsafe(s.album)
                        if s.has_key("track"): track = allsafe(s.track)
                        if s.has_key("time"): time = allsafe(s.time)
                        songs.append(dict(file=file, title=title, album=album, 
                                          artist=artist, track=track, time=time,
                                          updated=updated))

        self.songs, self.dirs = db.update(songs, self.dbDir, stats)
        obs_dbStats.value = stats
        obs_dbUpdating.value = False

    def songChange(self, item):
        try:
            if item.has_key('file'):
                id = self.songs.index(item['file'])
                self.db.markPlayed(id)
        except:
            print "Error in Player.songChange(), playcount not changed."

